package zheezes.eisp.compute.model;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.support.AbstractApplicationContext;

import zheezes.eisp.data.dao.DataAccessor;
import zheezes.eisp.data.orm.CalcTask;
import zheezes.eisp.util.SpringUtil;
import zheezes.util.Log4jUtil;
import zheezes.util.ProcLock;
import zheezes.util.Util;


public class Computer extends Observable implements Observer, Runnable {
	private static final Logger logger = Logger.getLogger(Computer.class);

	protected DataAccessor dataAccessor;
	protected CondChecker condChecker;

	List<CalcTask> tasklist = new ArrayList<CalcTask>();
	@SuppressWarnings("rawtypes")
	private Map<CalcTaskHandler, Future> tcl = new ConcurrentHashMap<CalcTaskHandler, Future>(); // task control list

	protected volatile boolean terminated = false;
	protected int interrupt = 1000;
	private static final ProcLock lock = new ProcLock(new File(System.getProperty("java.io.tmpdir"), "eisp_compute.lock"));
	
	private int thcnt = 4;
	private ExecutorService es;
	
	static {
		logger.debug("Loading");
	}

	public static void main(String[] args) {
		PropertyConfigurator.configure("log4j.properties");
		Log4jUtil.setLogFile("compute.log");
		AbstractApplicationContext context = SpringUtil.getApplicationContext();
		final Computer computer = (Computer) context.getBean("computer");
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				logger.info("Process shutdown");
				computer.stop();
			}
		});
		computer.run();
		// in the destroy function, only one code, close();
		context.destroy();
	}

	public CalcTaskHandler select(CalcTask task) {
		CalcTaskHandler handler = null;
		switch (task.getTaskType()) {
		case CalcTask.TASK_CALC_GROUP_STAT:
			handler = new GroupStatCalcTaskHandler();
			break;
		case CalcTask.TASK_CALC_USE:
			switch(task.getTargetType()) {
			case CalcTask.TARGET_CONCENTRATOR:
				handler = new ConResMeterDailyUseCalcTaskHandler();
				break;
			case CalcTask.TARGET_RESMETER:
				//TODO
				break;
			case CalcTask.TARGET_SUMMETER:
				handler = new SummatorDailyUseCalcTaskHandler();
				break;
			}
			break;
		case CalcTask.TASK_CALC_LOSSY:
			switch(task.getTargetType()) {
			case CalcTask.TARGET_ZONE:
				handler = new ZoneLossyCalcTaskHandler();
				break;
			}
			break;
		}
		if (handler != null) {
			handler.setTask(task);
		}
		return handler;
	}
	
	public void init() {
		es = Executors.newFixedThreadPool(thcnt);
	}

	@SuppressWarnings("rawtypes")
	public void run() {
		lock.addObserver(this);
		if(lock.lock() == 0) {
			while (!terminated) {
				tasklist = dataAccessor.queryAllQueueCalcTasks();
				if(tasklist == null || tasklist.size() == 0) {
					Util.msleep(interrupt);
					continue;
				}
				logger.debug(String.format("total %d calc tasks", tasklist.size()));
				setChanged();
				notifyObservers(tasklist.size());
				for (CalcTask task : tasklist) {
					logger.debug(task.desc());
					CalcTaskHandler handler = select(task);
					if (handler != null) {
						handler.setDataAccessor(dataAccessor);
						handler.setCondChecker(condChecker);
						logger.debug(handler.toString());
						try {
							tcl.put(handler, es.submit(handler));
						} catch (RejectedExecutionException e) {
							logger.debug(e.getMessage(), e);
							break;
						}
					}
				} // for list
				tasklist.clear();
				while (tcl.size() > 0) {
					for (Entry<CalcTaskHandler, Future> entry : tcl.entrySet()) {
						CalcTaskHandler handler = entry.getKey();
						Future future = entry.getValue();
						if (future.isDone() || future.isCancelled()) {
							tcl.remove(handler);
						}
					}
					Util.msleep(interrupt);
				}
			}
			lock.free();
		} else {
			setChanged();
			notifyObservers();
		}
	}
	
	@SuppressWarnings("rawtypes")
	public void terminate() {
		terminated = true;
		es.shutdownNow();
		while (tcl.size() > 0) {
			logger.debug(String.format("there is %d task running", tcl.size()));			
			for (Entry<CalcTaskHandler, Future> entry : tcl
					.entrySet()) {
				CalcTaskHandler handler = entry.getKey();
				handler.desc();
				handler.terminate();
				Future future = entry.getValue();
				if (future.isDone() || future.isCancelled()) {
					tcl.remove(handler);
				} else {
					future.cancel(true);
				}
			}
			Util.msleep(interrupt);
		}
	}
	
	public void stop() {
		terminate();
		// lock.free();
	}

	public void destroy() {
		stop();
	}
	
	public int runTaskCount() {
		return tcl.size();
	}
	
	/**
	 * by lock callback
	 */
	public void update(Observable o, Object arg) {
		if(o == lock) {
			// stop model
			terminate();
			// stop view
			setChanged();
			notifyObservers();
		}
	}

	public void setDataAccessor(DataAccessor dataAccessor) {
		this.dataAccessor = dataAccessor;
	}

	public void setCondChecker(CondChecker condChecker) {
		this.condChecker = condChecker;
	}

	public void setThcnt(int thcnt) {
		this.thcnt = thcnt;
	}
}
